package Grid;

import Data.Data;
import Element.Element;
import Element.Human;

//this class gather all the elements, print them and put them in side of each one

public class Grid {

    private int length;
    private int height;

    // number of elements

    private int nVide;
    private int[] nElement; // number of element for each class

    // the grid which will contains all the elements
    private Element[][] gridElement;

    public Grid(int nElement[], int nElementSick[]) {

        this.nElement = nElement;

        int nElem = 0;
        for (int i = 0; i < nElement.length; i++) {
            nElem += nElement[i];
        }

        defineLength(nElem);
        createRandomTab(nElem, nElementSick);

    }

    // give grid length define by the number of elements
    private void defineLength(int nElement) {

        // permet de définir la taille du tableau partir de nombre d'élément
        // ainsi que le nombre d'élément vide

        int l = (int) Math.sqrt(nElement);

        if ((l * l) == nElement) {
            this.length = l;
            this.height = l;
        } else if ((l * (l + 1)) >= nElement) {
            this.length = l + 1;
            this.height = l;
        } else if ((l * (l - 1)) > nElement) {
            this.length = l;
            this.height = l - 1;
        } else {
            this.length = l + 1;
            this.height = l + 1;
        }

        this.nVide = this.length * this.height - nElement;
    }

    static Element instanceElement(String s) {
        try {
            return (Element) Class.forName(s).newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
            System.err
                    .println("Instantiation Exception in Grid in instanceElement");
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            System.err
                    .println("Illegal access exception in Grid in instanceElement");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            System.err.println("Class not found : " + s);
            System.exit(0);

        }
        System.err.println("Pas de Class correspondante à " + s);
        System.exit(0);
        return null;

    }

    // this fill a tab randomly with element
    private void createRandomTab(int nElement, int[] nElementSick) {
        // creer un tableau aleatoire et le remplit avec le nombre d'element
        // demande

        Element[] tabElementOrdo = new Element[nElement + nVide];

        int i = 0; // i determine si l'element est malade ou pas
        int j = 0; // j parcourt les differente classe d'animaux, la classe
                   // human
        int k = 0; // k parcourt le tableau pour le remplir
        // boucle permettant de creer un tableau contenant tout les element et
        // des cases vides de maniere ordonnee

        // on remplit le tableau avec des elements des différentes classes
        while (j < this.nElement.length) {
            while (i < this.nElement[j]) {
                tabElementOrdo[k] = instanceElement(Data.className[j]);
                if (i < nElementSick[j]) {
                    tabElementOrdo[k].setSick(Data.elementIllness[j]);
                }
                i++;
                k++;
            }
            i = 0;
            j++;
        }

        // on remplit les cases cases restantes avec des elements vides
        while (k < nElement + nVide) {
            tabElementOrdo[k] = new Element();
            k++;
        }

        // on créer un tableau qui contient des nombre placé aléatoirement
        // allant de 0 à nElement
        int[] tab = tableauHasard(nElement + nVide);

        // on créer un tableau d'Element que l'on remplit de façon aléatoire
        // grace à tableauHasard
        Element[] tabElementRandom = new Element[nElement + nVide];

        for (i = 0; i < nElement + nVide; i++) {
            tabElementRandom[i] = tabElementOrdo[tab[i]];
        }

        // we create a 2D random tab
        this.gridElement = new Element[height][length];
        this.gridElement = tansfo1Dto2D(tabElementRandom, height, length);

    }

    /*
     * Méthode qui génère un tableau contenant des nombres au hasard allant de 0
     * à taille-1 avec chaque case du tableau qui contient un nombre différent;
     * exemple: taille=5: 2|4|1|0|3 exemple: taille=7: 4|2|5|0|7|1|3
     * 
     * L'objectif de l'alorithme est d'incrémenter un nombre que l'on va placer
     * dans des cases au hasard d'un tableau chaque nombre sera ensuite associé
     * à une image;
     */

    private int[] tableauHasard(int taille) {

        java.util.Random r = new java.util.Random();
        int numeroCase;

        int[] tableauHasard = new int[taille]; // on cree le tableau de nombre

        int i = 1;
        while (i < taille) { // on incrémente notre nombre à placer dans le
                             // tableau, i va de 1 à 7
            numeroCase = r.nextInt(taille - i + 1); // on choisie un nombre au
                                                    // hasard parmi les places
                                                    // restantes dans le tableau
                                                    // n1 va de 0 à 8-i

            for (int j = 0; j < taille; j++) {
                if (tableauHasard[j] != 0 && numeroCase >= j) {
                    numeroCase += 1;
                }
            }

            tableauHasard[numeroCase] = i;
            i++;
        }

        return tableauHasard;
    }

    // transform the 1D random tab into a 2D random tab
    private Element[][] tansfo1Dto2D(Element[] tabElementRandom, int l, int h) {

        Element[][] tab2D = new Element[l][h];

        int j = 0;
        int k = 0;

        for (int i = 0; i < tabElementRandom.length; i++) {

            tab2D[j][k] = tabElementRandom[i];

            // on incrément le tableau en 2D
            if (j < l - 1) {
                j++;
            } else {
                j = 0;
                k++;
            }
        }

        return tab2D;
    }

    // this fonction enables to change the grid when the day change
    // if there is a changement on a element or more it returns true if there is
    // no change it returns false

    public boolean nextDay() {

        boolean change = false;// check if there is a changement stat of the
                               // elements

        // here we improve the sick time and change the element status if needed
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < length; j++) {
                if (gridElement[i][j].isSick()) {

                    if (gridElement[i][j] instanceof Human) {
                        if (gridElement[i][j].getIsAlive()) {
                            change = true;
                        }
                        gridElement[i][j].illnessEvolution(true);
                    } else {
                        change = true;
                        gridElement[i][j].illnessEvolution(false);
                    }

                }
            }
        }

        // here we look if a sickness element give her illness to another
        for (int i = 0; i < height; i++) {
            for (int j = 0; j < length; j++) {

                if (gridElement[i][j].isContagious()) {
                    // test if the element transmit the illness
                    if (gridElement[i][j].infect(gridElement[(i - 1 + height)
                            % height][j].getName())) {
                        gridElement[(i - 1 + height) % height][j]
                                .setSick(gridElement[i][j].getIllness());
                    }
                    if (gridElement[i][j].infect(gridElement[(i + 1 + height)
                            % height][j].getName())) {
                        gridElement[(i + 1 + height) % height][j]
                                .setSick(gridElement[i][j].getIllness());
                    }
                    if (gridElement[i][j]
                            .infect(gridElement[i][(j - 1 + length) % length]
                                    .getName())) {
                        gridElement[i][(j - 1 + length) % length]
                                .setSick(gridElement[i][j].getIllness());
                    }
                    if (gridElement[i][j]
                            .infect(gridElement[i][(j + 1 + length) % length]
                                    .getName())) {
                        gridElement[i][(j + 1 + length) % length]
                                .setSick(gridElement[i][j].getIllness());
                    }

                }
            }
        }

        return change;
    }

    public int getLength() {
        return length;
    }

    public int getHeight() {
        return height;
    }

    public Element getElement(int l, int h) {
        return gridElement[l][h];
    }

}
